Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fake mod containers #192

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft

Conversation

LemmaEOF
Copy link
Contributor

@LemmaEOF LemmaEOF commented Jan 6, 2020

Mainly intended as a conversation starter, absolutely not merge-ready

Adds a system for adding "fake" mods which exist primarily as metadata storage and do not have any entrypoints called or mixins applied. Intended for systems which want to add "mods" as metadta for systems like Mod Menu to access. Possibly not helpful in the first place.

Currently runs the fake_mods entrypoint right after main is run. No system is yet provided for removing fake mods or calling after mods are loaded.

Copy link
Contributor

@i509VCB i509VCB left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few comments with a few questions.

One addition I would like to see is a way to get a list of fake/provided mods, possibly a:

Collection<ProvidedModContainer> getModsProvidedFrom(ModContainer);

One contract of that above method would be that, the ModContainer used to get the providers MUST be a real mod.

I think we should add a isFake method to ModContainer.

For an extended ModContainer type for the fake containers I think we should have a ProvidedModContainer where there is a getProvider method so we can figure out which mod provided the fake container.

@@ -30,6 +31,7 @@ public static void start(File runDir, Object gameInstance) {

FabricLoader.INSTANCE.prepareModInit(runDir, gameInstance);
EntrypointUtils.invoke("main", ModInitializer.class, ModInitializer::onInitialize);
FabricLoader.INSTANCE.getEntrypoints("fake_mods", FakeModProvider.class).forEach(provider -> FabricLoader.INSTANCE.appendFakeMods(provider.getFakeMods()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question I have is, is this the right spot for the FakeModProviders to run?

I personally think that we should load fake mods after the GameProvider is set but before onInitialize after each of the ModInitializers fires their constructors.
One key reason for doing this before Client/Common/Server entrypoints is a concern about mod loading order, where if you do this after the Common Initializer has gone off you don't exactly have much context in what all mods that exist at that point along with breaking the expectation of the ModInitalizers having a final state for mods being loaded.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main worry there is that then the fake mods likely haven't been loaded. You'd almost definitely want to do that in onInitialize.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is your reason for loading fake mods after the mod initializer (thereby the client/server would be loaded). Sponge's events cover some states near what on Initialize would cover.

* Should run <i>after</i> {@link ModInitializer#onInitialize()}
*/
public interface FakeModProvider {
Collection<ModContainer> getFakeMods();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be like below instead?

void registerFakeMods(Consumer<ProvidedModContainer> acceptor);

That would allow for a few things:
No need to worry about the whole collection being passed as null.
Anyone using the method would do the following for example:

public void registerFakeMods(Consumer<ProvidedModContainer> acceptor) {
    ProvidedModContainer container1 = ...
    ProvidedModContainer container2 = ...
    acceptor.accept(container1);
    acceptor.accept(container2);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that might be a good idea. I'll think about it.

@Override
public boolean isModLoaded(String id) {
return modMap.containsKey(id);
return modMap.containsKey(id) || fakeModMap.containsKey(id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My request for this would require a contract to be established that a IllegalArgumentException would be thrown if two ModContainers of the same modid were to be registered during the registration process.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh that's a good point.

@@ -0,0 +1,13 @@
package net.fabricmc.api;

import net.fabricmc.loader.api.ModContainer;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is currently not way to distinguish between a normal and fake mod container.
First I think we should add a isFake/isProvided method.

I think we should have return a ProvidedModContainer which extends ModContainer and a ModContainer getProvider(); so we could track which mod made the fake mod. Of course this would require a way to get what ModContainer provides an entrypoint (so we have a source of the FakeModProvider).

@@ -59,6 +59,12 @@ static FabricLoader getInstance() {
*/
Collection<ModContainer> getAllMods();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should getAllMods() return every single mod, regardless of weather it's fake or real and add a getRealMods() method.

Or should we make this exclusively real mods.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like it'd be better to have near-total separation between fake and real mods. I might even want to have isModLoaded have a counterpart isFakeModLoaded instead of the || behavior it has in the current state of the PR.

Copy link
Contributor

@Pyrofab Pyrofab Jan 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree with boundary here, I think mods should be all equal by default. If someone provides a "fake" mod with the same id as a real mod, there should be a good reason, and they should be treated the same.

@modmuss50
Copy link
Member

I’m not sure fake is the right word here. As it could well be something that resembles a real mod (to the user anyway).

We use builtin for the game. Provided mod? Proxy mod? I’m not really too sure.

@i509VCB
Copy link
Contributor

i509VCB commented Jan 7, 2020

We use builtin for the game. Provided mod? Proxy mod? I’m not really too sure.

I've been referring to these ProvidedModContainers. Proxy mod makes it sound like a real mod hides behind it.

@Pyrofab
Copy link
Contributor

Pyrofab commented Jan 7, 2020

I think we shouldn't limit this API to fake mods. There are several use cases in which provided mods should have full capabilities, actual fake mods are merely a special case. In fact, couldn't fake mods simply be regular mods with no source ?

@LemmaEOF
Copy link
Contributor Author

LemmaEOF commented Jan 7, 2020

Yeah, that is a good point. Maybe it'd be better for the provider to just let mods add new candidate finders to the ModResolver.

@LemmaEOF
Copy link
Contributor Author

LemmaEOF commented Jan 7, 2020

The only issue there is, of course, load problems. You shouldn't exactly have mods be able to add mod resolvers at a time when mods aren't even loaded yet. It seems like a big nightmare mess. Maybe we could use Java-native ServiceLoaders?

@Pyrofab
Copy link
Contributor

Pyrofab commented Jan 7, 2020

I think we would have to load mods in several passes, using native services is begging for classloading issues.

@LemmaEOF
Copy link
Contributor Author

LemmaEOF commented Jan 7, 2020

yeah, and loading mods in several passes seems like a wide open door for issues, along with kinda destroying one of the main points of fabric loader.

@asiekierka
Copy link
Contributor

Loading mods in several passes is not a big deal, as long as the passes are entirely data-driven and not decided by mod code - that is, they can still happen before classpath injection or any other part of loading mods.

@Pyrofab
Copy link
Contributor

Pyrofab commented Jan 7, 2020

But then that removes quite a bit of flexibility for those mod providers (eg. no conditional mod loading)

@modmuss50
Copy link
Member

One issue we had when adding mc as a builtin mod, was api was trying to load it as a resource pack when it shouldnt have done.

ModResourcePackUtil.java#L43

There will need to be a way for api to know if it should load the resources, and ideally done in a way without breaking existing api versions?

@i509VCB
Copy link
Contributor

i509VCB commented Jan 7, 2020

EDIT: I am going to write up a doc explaining my concept in much more detail.

So we all seem to be asking for different specs.

If we want full capability mod providers we have to load them before the game provider starts, which would thereby start mixin.

A data driven system does not go far enough for some contexts such as plugin loading (with sponge for example). I would suggest an entrypoint which allow you to define a mod with a container, and optionally the source of the mod within class path.

This entrypoint will be loaded on a special classloader which will crash if MC is referred to and has no access to api outside of fabric loader and some basic deps.

Addition 1:

I think the instant before preLaunch is invoked we should lock the mod loading process, no more providing mods.

Addition 2:

To use this system we should require a value in the fabric.mod.json be set (not just a custom value) to use the system, as a way to require an opt in instead of people just throwing their provide_mods entrypoint in and calling it a day.

With all mods which are provided by this system, their mod container should be an instanceof a ProvidedModContainer (As a way to distinguish between provided and regular mods).

A ProvidedModBuilder should be in the api to allow provided mods to be built by code, this will return a ProvidedModContainer with capability of specifying metadata down to even mixins and entrypoints.

To keep with the contractual detail of older versions, all ModContainers, once built should be immutable. Also pyrofab's mention of this in an above comment with possibly merging mod containers and metadata with same versions, I think we should crash and burn in that case since with jar mods, we don't support multiple mods with same ID.

@Pyrofab
Copy link
Contributor

Pyrofab commented Jan 7, 2020

My comment was regarding a provided mod that has the id of a real mod, but with the latter not being present. In that case, there is a decent chance the provided mod is supposed to work exactly like the missing real mod.

@liach
Copy link
Contributor

liach commented Mar 19, 2021

Is this covered by the "provides" functionality?

@LemmaEOF
Copy link
Contributor Author

As far as I can tell, no; provides is statically-defined and so wouldn't work for loaders that may provide arbitrary mods depending on what's in their loading directory.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants